#ifndef TINX_FS_EXT2_H
#define TINX_FS_EXT2_H

#include <tinx/buffer.h>
#include <tinx/fs.h>

#define EXT2_MAGIC 0xEF53

// & 0xF000
#define EXT2_S_IFSOCK 0xC000
#define EXT2_S_IFLNK 0xA000
#define EXT2_S_IFREG 0x8000
#define EXT2_S_IFBLK 0x6000
#define EXT2_S_IFDIR 0x4000
#define EXT2_S_IFCHR 0x2000
#define EXT2_S_IFIFO 0x1000

// I think they took a bit of inspiration from FAT*
#define EXT2_ROOT_INODE 2

// Required feature Flags
#define EXT2_R_F_COMPRESSION 0x0001
#define EXT2_R_F_TYPE_FIELD 0x0002
#define EXT2_R_F_JOURNAL_REPLAY 0x0004
#define EXT2_R_F_JOURNAL_DEVICE 0x0008

// FileSystem State
#define EXT2_FS_S_CLEAN 1
#define EXT2_FS_S_ERRORS 2

// FileSystem Error handling
#define EXT2_FS_E_IGNORE 1
#define EXT2_FS_E_REMOUNT_RO 2
#define EXT2_FS_E_KPANIC 3

#define BLOCK_SIZE (data->block_size)
#define BLOCK_BITS (BLOCK_SIZE * 8)
#define BLOCK_INODES (BLOCK_SIZE / data->inode_size)
#define BLOCK_INDEXES (BLOCK_SIZE / sizeof(u32))         // 块索引数量

#define DIRECT_BLOCK (12)                                                       // 直接块数量
#define INDIRECT1_BLOCK BLOCK_INDEXES                                           // 一级间接块数量
#define INDIRECT2_BLOCK (INDIRECT1_BLOCK * INDIRECT1_BLOCK)                     // 二级间接块数量
#define INDIRECT3_BLOCK (INDIRECT1_BLOCK * INDIRECT1_BLOCK * INDIRECT1_BLOCK)   // 三级间接块数量
#define TOTAL_BLOCK (DIRECT_BLOCK + INDIRECT1_BLOCK + INDIRECT2_BLOCK + INDIRECT3_BLOCK)          // 全部块数量

// 获得 inode 所在的组
#define INODE_TO_GROUP(nr, desc) ((nr - 1) / desc->group_inodes)

// 获得 inode 在组中的索引
#define INODE_TO_GROUP_INDEX(nr, desc) ((nr - 1) % desc->group_inodes)

#define ENTRY_LEN(namelen) (div_round_up(sizeof(ext2_dentry_t) + namelen, 4) << 2)

typedef struct ext2_desc_t
{
    u32 total_inodes;
    u32 total_blocks;
    u32 suser_blocks;
    u32 free_blocks;
    u32 free_inodes;
    u32 superblock;
    u32 block_size;         //do 1024 << block_size to get the block size
    u32 frag_size;      //Do 1024 << fragment_size to get the fragment size
    u32 group_blocks;
    u32 group_frags;
    u32 group_inodes;
    u32 mtime;
    u32 wtime;
    u16 times_mounted;      // Since last fsck
    u16 mounts_allowed;     // fsck must be done after this amount surpassed
    u16 magic;
    u16 state;
    u16 error_action;
    u16 version_minor;
    u32 last_check;          // POSIX time of last fsck
    u32 check_interval;      // POSIX time between fscks
    u32 os_id;
    u32 version_major;
    u16 reserved_user;
    u16 reserved_group;
    u32 first_inode;
    u16 inode_size;
    u16 superblock_group;
    u32 optional_features;
    u32 required_features;
    u32 ro_features;
    u8 filesystem_id[16];
    u8 volume_name[16];
    u8 last_mount_path[64];
    u32 compression;
    u8 file_prealloc_blocks;
    u8 directory_prealloc_blocks;
    u16 unused;
    u8 journal_id[16];
    u32 journal_inode;
    u32 journal_device;
    u32 orphan_inode_head;
} ext2_desc_t;

typedef struct ext2_group_desc_t
{
    u32 zmap;          /* Blocks bitmap block */
    u32 imap;          /* Inodes bitmap block */
    u32 inode_table;   /* Inodes table block */
    u16 free_blocks;   /* Free blocks count */
    u16 free_inodes;   /* Free inodes count */
    u16 used_dirs;     /* Directories count */
    u16 RESERVED[7];
} ext2_group_desc_t;

typedef struct ext2_inode_t
{
    u16 mode;
    u16 uid;
    u32 size;
    u32 atime;
    u32 ctime;
    u32 mtime;
    u32 dtime;
    u16 gid;
    u16 nlinks;
    u32 blocks;
    u32 flags;
    u32 RESERVED;
    u32 zone[DIRECT_BLOCK + 3];
    u32 version;
    u32 file_acl;
    union
    {
        u32 dir_acl;
        u32 size_high;
    };
    u32 faddr;
    u8 frag;
    u8 fsize;
    u16 RESERVED[5];
} ext2_inode_t;

typedef struct ext2_dentry_t
{
    u32 nr;
    u16 size;
    u8 namelen;
    u8 type;
    char name[0];
} ext2_dentry_t;

typedef struct ext2_group_info_t
{
    buffer_t *imap;
    buffer_t *zmap;
    ext2_group_desc_t *desc;
} ext2_group_info_t;

typedef struct ext2_data_t
{
    u32 total_groups;
    buffer_t *groups_buf;
    ext2_group_info_t *group_infos;

    u32 inode_size;
    u32 block_size;
} ext2_data_t;

u32 ext2_ialloc(super_t *super, u32 group);
u32 ext2_balloc(super_t *super, u32 group);

void ext2_ifree(super_t *super, u32 nr);
void ext2_bfree(super_t *super, u32 block);

u32 ext2_bmap(inode_t *inode, u32 block, bool creat);

inode_t *ext2_iget(super_t *super, u32 nr);

int ext2_truncate(inode_t *inode);

buffer_t *ext2_add_entry(inode_t *dir, char *name, ext2_dentry_t **entry);
buffer_t *ext2_find_entry(inode_t **dir, char *name, char **next, ext2_dentry_t **entry);

int ext2_permission(inode_t *inode, int mask);
int ext2_lnamei(inode_t *dir, inode_t *inode, inode_t **result);

#endif